package kgame5.pbe.animation {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;




	public final class AnimationPlayer extends Sprite
	{
		private var _framesPassed:int;
		private var _totalFrames:int;
		private var _currentFrame:Frame;
		private var _frameIndex:int;
		private var _frames:Array;
		private var _framesMap:Dictionary;
		private var _isLooping:Boolean;

		private var _rotationFrame:int;
		private var _hasRotationChanged:Boolean;
		
		private var _hasAnimationEnded:Boolean;
		private var _animationDelay:int;
		private var _timeSinceLastUpdate:int;
		private var _currentAnimation:Animation;
		private var _isPlaying:Boolean;
		private var _animationMap:Dictionary;
		private var _animationQueue:Array;
		
		private var _bitmap:Bitmap;
		private var _timer:Timer;
		private var _timerInterval:int=0;

		public function AnimationPlayer()
		{
			_IDArray=[];
			_animationQueue=[];
			_frames=[];
			_animationMap=new Dictionary();
			_framesMap=new Dictionary();
			_bitmap=new Bitmap(null, "never", false);
			addChild(_bitmap);
		}
		
		private var _rotation:Number=0;
		public override function set rotation(value:Number):void
		{
			_rotation=int(value);
			//Make sure value is between 0 and 360
			_rotation%=360;
			if (_rotation < 0)
			{
				_rotation+=360;
			}
			_rotationFrame=Math.round(_rotation / (_currentAnimation._degToRotBy));

			//If we are at the 360 frame, set back down to 0 to
			//eliminate errors
			if (_rotationFrame >= _currentAnimation._framesOfRotation)
			{
				_rotationFrame=0;
			}
			performOffset();
			_hasRotationChanged=true;
		}
		
		private var _desiredX:Number=0;
		public override function get rotation():Number
		{
			return _rotation;
		}

		public override function set x(value:Number):void
		{
			_desiredX=value;
			performOffset();
		}

		public override function get x():Number
		{
			return _desiredX;
		}
		
		private var _desiredY:Number=0;
		public override function set y(value:Number):void
		{
			_desiredY=value;
			performOffset();
		}

		public override function get y():Number
		{
			return _desiredY;
		}

		override public function set scaleX(v:Number):void
		{
			super.scaleX=v;
			performOffset();
		}

		override public function set scaleY(v:Number):void
		{
			super.scaleY=v;
			performOffset();
		}

		/**
		 * Set the frame rate of the animation.
		 * @param value frame rate per second
		 */
		private var _fps:int; 
		public function set fps(value:int):void
		{
			_fps=value;
			if (value <= 0)
			{
				_fps=0;
				_animationDelay=0;
			}
			else
			{
				_fps=value;
				_animationDelay=1000 / value;
			}
		}

		public function get fps():int
		{
			return _fps;
		}

		/**
		 * @return The ID of the current animation that plays by default once all other animations are finished.
		 */
		private var _defaultAnimationID:String;
		public function get defaultAnimationID():String
		{
			return _defaultAnimationID;
		}

		/**
		 * Set the default animation to this ID. In other words, the animation that plays once all others are complete.
		 * @param value The ID of the animation.
		 *
		 */
		public function set defaultAnimationID(v:String):void
		{
			_defaultAnimationID=v;
		}

		/**
		 * @return The ID of the current animation that is playing
		 */
		private var _currentAnimationID:String;
		public function get currentAnimationID():String
		{
			return _currentAnimationID;
		}

		/**
		 * @return An array list of all ID's associated with this player
		 */
		private var _IDArray:Array; //used for copy function 
		public function get animationIDs():Array
		{
			return _IDArray.slice();
		}

		/**
		 * @return The current frame's offset from ( 0, 0 )
		 */
		public function get offsetX():Number
		{
			return _currentFrame.offsetX[_rotationFrame];
		}

		/**
		 * @return The current frame's offset from ( 0, 0 )
		 */
		public function get offsetY():Number
		{
			return _currentFrame.offsetY[_rotationFrame];
		}

		public override function get width():Number
		{
			return _currentFrame.width[_rotationFrame];
		}

		public override function set width(value:Number):void
		{
			throw new Error("<AnimationPlayer> Width / Height cannot be changed!");
		}

		public override function get height():Number
		{
			return _currentFrame.height[_rotationFrame];
		}

		public override function set height(value:Number):void
		{
			throw new Error("<AnimationPlayer> Width / Height cannot be changed!");
		}
		
		public function  findAnimation(animationID:String):Animation
		{
			return _animationMap[animationID];
		}


		/**
		 * Add a new animation to this player. If it is the first animation, it will be set to the default
		 * @param animationID The ID to reference this animation by
		 * @param animation The instance of the animation to be used
		 *
		 */
		public function addAnimation(animationID:String, animation:Animation):void
		{
			_IDArray.push(animationID);
			_animationMap[animationID]=animation;
			if (!_defaultAnimationID)
			{
				_defaultAnimationID=animationID;
				play(animationID);
				performOffset();
			}
		}

		/**
		 * switches playhead to this animation immediately
		 * @param animationID The ID of the animation to switch to
		 *
		 */
		public function play(animationID:String):void
		{
			if (!_animationMap[animationID])return;
			_isPlaying=true;
			_hasAnimationEnded=false;
			_animationQueue=[_currentAnimation];
			startNewAnimation(animationID);
		}

		/**
		 * Jumps to a specific frame of animation
		 * @param frameID The ID of the frame to jump to
		 * @param animationID The ID of the animation that the frame is located in. If left as null, the current animation will be used
		 *
		 */
		public function gotoFrame(frameID:String, animationID:String=null):void
		{
			if (animationID)
			{
				startNewAnimation(animationID);
			}
			var index:int=_framesMap[frameID];
			_currentFrame=Frame(_frames[index]);
			performOffset();
			_bitmap.bitmapData=_currentFrame.bitmapData[_rotationFrame] as BitmapData;
			_frameIndex=index;
			_framesPassed=0;
			_isPlaying=false;
			if (_currentFrame.frameID)
			{
				if (hasEventListener(FrameEvent.CHANGE))
				{
					dispatchEvent(new FrameEvent(FrameEvent.CHANGE, true, _currentFrame.frameID, _currentAnimationID));
				}
			}
		}

		public function isPaused():Boolean
		{
			return _isPlaying;
		}

		public function unpause():void
		{
			if (_currentAnimation)
			{
				_isPlaying=true;
			}
		}

		public function pause():void
		{
			_isPlaying=false;
		}

		/**
		 * Use this function to have the AnimationPlayer update itself. The AnimationPlayer MUST be added to the stage. Updating will
		 * stop once it is removed from the stage.
		 * @param a_interval How often update is to be called ( in ms )
		 */
		public function startSelfTick(interval:int):void
		{
			if (interval <= 0)return;
			stopSelfTick();
			_timerInterval=interval;
			_timer=new Timer(_timerInterval);
			_timer.addEventListener(TimerEvent.TIMER, tickTimer, false, 0, true);
			_timer.start();
			this.addEventListener(Event.REMOVED_FROM_STAGE, handleRemovedFromStage, false, 0, true);
		}

		/**
		 * Cancel self updating
		 *
		 */
		public function stopSelfTick():void
		{
			if (_timer)
			{
				this.removeEventListener(Event.REMOVED_FROM_STAGE, handleRemovedFromStage, false);
				_timer.removeEventListener(TimerEvent.TIMER, tickTimer, false);
				_timer.stop();
				_timer=null;
			}
		}

		/**
		 * Updates the AnimationPlayer. Must be called each game tick manually, unless StartSelfTick is set.
		 * @param ms The amount of time passed since the last update ( in ms )
		 */
		public function update(ms:Number):void
		{
			if (_hasAnimationEnded)
			{
				if (hasEventListener(AnimationEvent.CHANGE))
				{
					dispatchEvent(new AnimationEvent(AnimationEvent.CHANGE, false, _currentAnimationID));
				}

				if (_isLooping)
				{
					startNewAnimation(_animationQueue[0]);
				}
				else if (_animationQueue.length <= 1)
				{
					_animationQueue.splice(0);

					if (_currentAnimationID == _defaultAnimationID)
					{
						_isPlaying=false;
					}
					else
					{
						startNewAnimation(_defaultAnimationID);
					}
				}
				else
				{
					_animationQueue.shift();
					startNewAnimation(_animationQueue[0]);
				}

				_hasAnimationEnded=false;
			}
			else if (_hasRotationChanged)
			{
				_bitmap.bitmapData=_currentFrame.bitmapData[_rotationFrame];
				_hasRotationChanged=false;
			}

			if (_isPlaying)
			{
				_timeSinceLastUpdate+=ms;
				if (_timeSinceLastUpdate > _animationDelay)
				{
					_timeSinceLastUpdate-=_animationDelay;

					updateFrame();
				}
			}
		}

		/**
		 * adds the id to an animation queue
		 * when the active animation completes
		 * the next animation in the queue is begun
		 * @param animationID
		 *
		 */
		public function queue(animationID:String):void
		{
			var animation:Animation=_animationMap[animationID];
			if (!animation)return;
			_animationQueue.push(animationID);
		}

		/**
		 * Copies this animation player for reuse
		 * @return A duplicate version of this AnimationPlayer
		 *
		 */
		public function copy():AnimationPlayer
		{
			var player:AnimationPlayer=new AnimationPlayer();
			for (var i:int=0; i < _IDArray.length; i++)
			{
				var id:String=_IDArray[i];
				player.addAnimation(id, _animationMap[id]);
			}

			player.defaultAnimationID=_defaultAnimationID;
			player.fps=_fps;
			player.play(_defaultAnimationID);
			player._bitmap.bitmapData=_bitmap.bitmapData;

			return player;
		}


		private function handleRemovedFromStage(e:Event):void
		{
			stopSelfTick();
		}

		private function tickTimer(e:TimerEvent):void
		{
			update(_timerInterval);
		}

		private function performOffset():void
		{
			var signX:int=-1;
			var signY:int=-1;

			if (scaleX < 0)
			{
				signX=1;
			}
			if (scaleY < 0)
			{
				signY=1;
			}

			super.x=_desiredX + signX * offsetX;
			super.y=_desiredY + signY * offsetY;
		}

		private function startNewAnimation(animationID:String):void
		{
			_timeSinceLastUpdate=0;
			_currentAnimationID=animationID;
			_currentAnimation=_animationMap[_currentAnimationID];

			if (!_currentAnimation)
			{
				_currentAnimationID=_defaultAnimationID;
				_currentAnimation=_animationMap[_currentAnimationID];
			}

			fps=_currentAnimation._fps;
			_frames=_currentAnimation._frames;
			_framesMap=_currentAnimation._frameMap;
			_isLooping=_currentAnimation.isLooping;

			_totalFrames=_frames.length;
			_frameIndex=0;
			_currentFrame=Frame(_frames[0]);

			_bitmap.bitmapData=_currentFrame.bitmapData[_rotationFrame];

			performOffset();

			if (hasEventListener(AnimationEvent.CHANGE))
			{
				dispatchEvent(new AnimationEvent(AnimationEvent.CHANGE, true, _currentAnimationID));
			}
		}

		private function updateFrame():void
		{
			if (!_currentFrame)return;

			_framesPassed++;
			if (_framesPassed > _currentFrame.numFrames)
			{
				_framesPassed=0;
				if (++_frameIndex >= _totalFrames)
				{
					//老的，会存在时序问题的bug,如果外部受到END时间，并调用了play()方法，则还会执行_hasAnimationEnded=true;
					//导致了错误
//					dispatchEvent(new FrameEvent(FrameEvent.END, false, _currentFrame.frameID, _currentAnimationID));
//					if (!_isLooping)
//					{
//						_hasAnimationEnded=true;
//						return;
//					}
//					else
//					{
//						_frameIndex=0;
//					}
					
					//max+ 解决时序问题
					
					if (!_isLooping)
					{
						_hasAnimationEnded=true;
						dispatchEvent(new FrameEvent(FrameEvent.END, false, _currentFrame.frameID, _currentAnimationID));
						return;
					}
					else
					{
						dispatchEvent(new FrameEvent(FrameEvent.END, false, _currentFrame.frameID, _currentAnimationID));
						_frameIndex=0;
					}
				}

				_currentFrame=Frame(_frames[_frameIndex]);
				
				_bitmap.bitmapData=_currentFrame.bitmapData[_rotationFrame];

				performOffset();

				if (_currentFrame.frameID)
				{
					if (hasEventListener(FrameEvent.CHANGE))
					{
						dispatchEvent(new FrameEvent(FrameEvent.CHANGE, true, _currentFrame.frameID, _currentAnimationID));
					}
				}
			}
		}

	}
}
